#!/usr/bin/perl
#	IPS/IDS data collector. Uses SDEE protocol
#	Usage:
#
#	sdee.pl [/path/to/config.file]
#	
#	Default config file - /usr/local/etc/sdee.conf
#
#(c) 2008 Alexander Klepikov klepikov.alex@gmail.com

use Net::SDEE;
#Useful for debug
#use Data::Dumper;
use POSIX qw(strftime);
use DBI;
use Config::General;

#Declare global vars and defaults
#DB handle
my $dbh;
#Subscription ID file
my $subId_file = "/var/run/sdee";
#Saved subscription ID
my $saved_subId = undef;
#Content and subscrition ID
my $content, $subId;
#Username and password
my $user = "cisco";
my $pass = "cisco";
#Sensor type
#ips - for Cisco IOS IPS
#cids - for Cisco IDS module
my $sensor_type = "ips";
#Sensor host
my $host = "sensor.your.domain.com";
#Database name
my $dbname="sdee";
#Database driver - see man DBI
my $dbdriver="Pg";
#Database table
my $dbtable="sdee";
#Database host
my $dbhost="sql.your.domain.com";
#Database username
my $username="pgsql";
#Database password
my $password="";
#Default config file
my $config_file = "/usr/local/etc/sdee.conf";
#Get config file from command line
if (@ARGV >= 1)
{
  $config_file = $ARGV[0];
}

#This subroutine is called every time we got data from SDEE server.
#It gets all data from XML sheet, forms and executes SQL query.
#Careful - woodoo magic ;)
sub eventCallback
{
  my ($event, $subscriptionID) = @_;
  #Print everything we got from SDEE server
  #print Dumper($event);
  my $eventId=$event->{ 'eventId' };
  my $vendor=$event->{ 'vendor' };
  my $severity=$event->{ 'severity' };
  my $originator=$event->{ 'sd:originator' }->{ 'sd:hostId' };
  my $t=$event->{ 'sd:time' }->{ 'content' };
  #Approximate time to within 1 second
  $t=$t/1000000000;
  my $time = strftime "%Y-%m-%d %H:%M:%S UTC", gmtime($t);
  my $time_offset=$event->{ 'sd:time' }->{ 'offset' }/60;
  my $signature_description=$event->{ 'sd:signature' }->{'description'};
  my $signature_id=$event->{ 'sd:signature' }->{'id'};
  my $signature_subsigId=$event->{ 'sd:signature' }->{'cid:subsigId'};
  my $signature_version=$event->{ 'sd:signature' }->{'version'};
  #Outputs slightly differs for Cisco IPS and Cisco IDS
  if ($sensor_type =~ "^cids\$") { $signature_version=$event->{ 'sd:signature' }->{'cid:version'}; }
  my $signature_sigDetails=$event->{ 'sd:signature' }->{'cid:sigDetails'};
  my $signature_marsCategory=$event->{ 'sd:signature' }->{'marsCategory'};
  my $interfaceGroup=$event->{ 'sd:interfaceGroup' };
  my $vlan=$event->{ 'sd:vlan' };
  my $participants_attacker_addr=$event->{ 'sd:participants' }->{'sd:attacker'}->{'sd:addr'};
  if ( $participants_attacker_addr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
    $participants_attacker_addr = "NULL";
  } else {
    $participants_attacker_addr = "'$participants_attacker_addr'";
  }
  my $participants_attacker_port=$event->{ 'sd:participants' }->{'sd:attacker'}->{'sd:port'};
  if ( $participants_attacker_port !~ /^\d/ ) {
    $participants_attacker_port = "NULL";
  } else {
    $participants_attacker_port = "'$participants_attacker_port'";
  }
  my $participants_attacker_locality=$event->{ 'sd:participants' }->{'sd:attacker'}->{'sd:addr'}->{'cid:locality'};
  my $participants_target_addr=$event->{ 'sd:participants' }->{'sd:target'}->{ 'sd:addr' };
  if ( $participants_target_addr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
    $participants_target_addr = "NULL";
  } else {
    $participants_target_addr = "'$participants_target_addr'";
  }
  my $participants_target_port=$event->{ 'sd:participants' }->{'sd:target'}->{ 'sd:port'};
  if ( $participants_target_port !~ /^\d/ ) {
    $participants_target_port = "NULL";
  } else {
    $participants_target_port = "'$participants_target_port'";
  }
  my $participants_target_locality=$event->{ 'sd:participants' }->{'sd:target'}->{'sd:addr'}->{'cid:locality'};
  my $context_fromAttacker=$event->{ 'cid:context' }->{'cid:fromAttacker'};
  my $alertDetails=$event->{ 'cid:alertDetails' };
  #Output looks like "SlotNum 0 SubslotNum 1 Portnum 0 SubintNum 10 IntType 18 VlanId 2 ;"
  #Parse it
  #my ($a1,$alertdetails_slotnum,$a3,$alertdetails_subslotnum,$a5,$alertdetails_portnum,$a7,$alertdetails_subintnum,$a9,$alertdetails_inttype,$a11,$alertdetails_vlanid) = split(/[ +]/,$alertDetails,13);
  my ($alertdetails_slotnum,$alertdetails_subslotnum,$alertdetails_portnum,$alertdetails_subintnum,$alertdetails_inttype,$alertdetails_vlanid);
  if ($alertDetails =~ /SlotNum (\d*) SubSlotNum (\d*) PortNum (\d*) SubIntNum (\d*) IntType (\d*) VlanId (\d*)/) {
   ($alertdetails_slotnum,$alertdetails_subslotnum,$alertdetails_portnum,$alertdetails_subintnum,$alertdetails_inttype,$alertdetails_vlanid) = ($1,$2,$3,$4,$5,$6);
   }
   
  if ( $alertdetails_slotnum !~ /\d/ )
  {
    $alertdetails_slotnum="NULL";
  } else {
    $alertdetails_slotnum="'$alertdetails_slotnum'";
  }
  if ( $alertdetails_subslotnum !~ /\d/ )
  {
    $alertdetails_subslotnum="NULL";
  } else {
    $alertdetails_subslotnum="'$alertdetails_subslotnum'";
  }
  if ( $alertdetails_portnum !~ /\d/ )
  {
    $alertdetails_portnum="NULL";
  } else {
    $alertdetails_portnum="'$alertdetails_portnum'";
  }
  if ( $alertdetails_subintnum !~ /\d/ )
  {
    $alertdetails_subintnum="NULL";
  } else {
    $alertdetails_subintnum="'$alertdetails_subintnum'";
  }
  if ( $alertdetails_inttype !~ /\d/ )
  {
    $alertdetails_inttype="NULL";
  } else {
    $alertdetails_inttype="'$alertdetails_inttype'";
  }
  if ( $alertdetails_vlanid !~ /\d/ )
  {
    $alertdetails_vlanid="NULL";
  } else {
    $alertdetails_vlanid="'$alertdetails_vlanid'";
  }

  my $riskRatingValue=$event->{ 'cid:riskRatingValue' }->{'content'};
  my $riskRatingValue_targetValueRating=$event->{ 'cid:riskRatingValue' }->{'targetValueRating'};
  my $riskRatingValue_attackRelevanceRating=$event->{ 'cid:riskRatingValue' }->{'attackRelevanceRating'};
  my $threatRatingValue=$event->{ 'cid:threatRatingValue' };
  my $ids_interface=$event->{ 'cid:interface' };
  my $protocol=$event->{ 'cid:protocol' };
  my $sql;
  if ($sensor_type =~ "^ips\$")
  {
    #Generic SQL INSERT query, I hope
    $sql="INSERT INTO $dbtable (eventid,vendor,severity,originator,time,signature_description,".
    "signature_id,signature_subsigid,signature_version,signature_sigdetails,participants_attacker_addr,".
    "participants_attacker_port,participants_target_addr,participants_target_port,protocol) ".
    "VALUES ('$eventId','$vendor','$severity','$originator','$time','$signature_description',".
    "'$signature_id','$signature_subsigId','$signature_version','$signature_sigDetails',$participants_attacker_addr,".
    "$participants_attacker_port,$participants_target_addr,$participants_target_port,'$protocol')";
  }
  if ($sensor_type =~ "^cids\$")
  {
    #Outputs slightly differs for Cisco IPS and Cisco IDS
    $participants_attacker_addr=$event->{ 'sd:participants' }->{'sd:attacker'}->{'sd:addr'}->{ 'content' };
    if ( $participants_attacker_addr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
      $participants_attacker_addr = "NULL";
    } else {
      $participants_attacker_addr = "'$participants_attacker_addr'";
    }
    $participants_target_addr=$event->{ 'sd:participants' }->{'sd:target'}->{ 'sd:addr' }->{ 'content' };
    if ( $participants_target_addr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
      $participants_target_addr = "NULL";
    } else {
      $participants_target_addr = "'$participants_target_addr'";
    }
    #Generic SQL INSERT query, I hope
    $sql="INSERT INTO $dbtable (eventid,vendor,severity,originator,time,signature_description,".
    "signature_id,signature_subsigid,signature_version,signature_sigdetails,signature_marscategory,".
    "interfacegroup,vlan,participants_attacker_addr,".
    "participants_attacker_port,participants_attacker_locality,participants_target_addr,".
    "participants_target_port,participants_target_locality,context_fromattacker,alertdetails,".
    "alertdetails_slotnum,alertdetails_subslotnum,alertdetails_portnum,alertdetails_subintnum,alertdetails_inttype,".
    "riskratingvalue,riskratingvalue_targetvaluerating,riskratingvalue_attackrelevancerating,".
    "threatratingvalue,ids_interface,protocol) ".
    "VALUES ('$eventId','$vendor','$severity','$originator','$time','$signature_description',".
    "'$signature_id','$signature_subsigId','$signature_version','$signature_sigDetails','$signature_marsCategory',".
    "'$interfaceGroup','$vlan',$participants_attacker_addr,".
    "$participants_attacker_port,'$participants_attacker_locality',$participants_target_addr,".
    "$participants_target_port,'$participants_target_locality','$context_fromAttacker','$alertDetails',".
    "$alertdetails_slotnum,$alertdetails_subslotnum,$alertdetails_portnum,$alertdetails_subintnum,$alertdetails_inttype,".
    "'$riskRatingValue','$riskRatingValue_targetValueRating','$riskRatingValue_attackRelevanceRating',".
    "'$threatRatingValue','$ids_interface','$protocol')";
  }
  #Print SQL query
  #print $sql, "\n";
  my $rv = $dbh->do($sql);
  #Print query result
  #if (!defined $rv) {
  #	print "Error doing query '$sql': ".$dbh->errstr."\n";
  #} else {
  #	print "$rv rows affected\n";
  #}
}

#This should be error handler
sub errorCallback
{
  my $message = shift;
  #Net::SDEE error message
  #printf "$message\n";
}

$sdee = Net::SDEE->new(
	maxNbrOfEvents => 1,
	returnevents => 1,
	unsubscribeOnExit => 0,
	debug => 1,
	callback => \&eventCallback,
	debug_callback => \&errorCallback
);

#Read config
$conf = new Config::General( -ConfigFile     => $config_file,
		-ExtendedAccess => 1,
		-InterPolateVars => 1);
my %config = $conf->getall;

$subId_file = %config->{ subId_file } if $conf->exists('subId_file');
$user = %config->{ username } if $conf->exists('username');
$pass = %config->{ password } if $conf->exists('password');
$host = %config->{ host } if $conf->exists('host');
$sensor_type = %config->{ sensor_type } if $conf->exists('sensor_type');
$dbhost = %config->{ dbhost } if $conf->exists('dbhost');
$dbdriver = %config->{ dbdriver } if $conf->exists('dbdriver');
$dbname = %config->{ dbname } if $conf->exists('dbname');
$dbtable = %config->{ dbtable } if $conf->exists('dbtable');
$username = %config->{ dbusername } if $conf->exists('dbusername');
$password = %config->{ dbpassword } if $conf->exists('dbpassword');

$sdee->Username($user);
$sdee->Password($pass);
$sdee->Server($host);
#Do we need this?
#$sdee->Port($port);
#$sdee->Scheme("https");
$sdee->maxNbrOfEvents(100);

#Try to read subscritpion ID from file
if (open F, "<$subId_file") {
	$saved_subId = <F>;
}

#Resume subscription or create new
if (defined($saved_subId)) {
	$subId=$sdee->resumeSubscription( subscriptionId => $saved_subId);
} else {
	($content,$subId)=$sdee->open();
	open F, ">$subId_file";
	print F $subId;
}

#Connect to DB
$dbh = DBI->connect("dbi:$dbdriver:dbname=$dbname;host=$dbhost","$username","$password",
				{PrintError => 0});

#Report in case of error
if ($DBI::err != 0) {
  print $DBI::errstr . "\n";
  exit($DBI::err);
}

# callback subs are called for each event

$sdee->get($subId);

#Write subscription to file
#TODO
#Write error handler
if ($sdee->{ _session }->state =~ "^closed\$") {
	($content,$subId)=$sdee->open();
	open F, ">$subId_file";
	print F $subId;
}

#Do cleanup
close F;
$dbh->disconnect();

